跳到主要内容

大文件下载、断点续传怎么设计?

考察点​

  • HTTP Range 与分片下载
  • 下载任务的状态机与持久化(agent 场景:重启后继续下)

参考答案​

1. 核心原理:Range 请求​

HTTP 支持按字节范围下载:

GET /package.zip HTTP/1.1
Range: bytes=1048576-2097151 // 下载第 2-3 MB

服务器支持则返回 206 Partial Content 和对应分片;返回 200 说明不支持 Range,断点续传不可用(需降级为整体重下)。

先探测:

const res = await fetch(url, { method: 'HEAD' });
const totalSize = Number(res.headers.get('content-length'));
const supportRange = res.headers.get('accept-ranges') === 'bytes';

2. 下载任务设计​

(1)任务状态机

pending → downloading ⇄ paused → completed
↓
failed → (重试) → downloading

(2)进度持久化(断点续传的关键)

每个任务落盘一份元数据,记录已下载的分片状态:

// 任务元数据(存磁盘,如 userData/downloads/xxx.json)
{
"url": "https://cdn.example.com/pkg.zip",
"savePath": "/Users/xxx/Downloads/pkg.zip",
"totalSize": 104857600,
"chunkSize": 4194304, // 4MB 一个分片
"completedChunks": [0, 1, 2, 5], // 已完成的分片序号
"etag": "\"abc123\"", // 校验文件是否变了
"status": "paused"
}

应用重启后扫描未完成任务 → 读元数据 → 只下载缺失分片。

(3)分片下载 + 写入

async function downloadChunk(url, index, chunkSize, fd) {
const start = index * chunkSize;
const end = start + chunkSize - 1;

const res = await fetch(url, {
headers: { Range: `bytes=${start}-${end}` },
});

const buffer = Buffer.from(await res.arrayBuffer());
// 直接写到目标文件的对应偏移位置
await fd.write(buffer, 0, buffer.length, start);
}

优化:预分配文件大小(ftruncate),分片乱序写入,全部完成后无需合并。

(4)并发与限速

  • 同时下载 3-6 个分片(并发太高 CDN 会限速,收益递减)
  • 任务队列管理:同时下载任务数限制(如 2 个),其余排队
  • 限速:stream 层做令牌桶(agent 在后台下载时不能打满用户带宽)

3. 完整性校验​

  • ETag 变化检测:续传前重新 HEAD,etag 变了说明文件更新,旧分片作废重新下
  • 最终校验:服务端提供 md5/sha256,下载完成后整文件校验,不匹配则重下损坏分片或整体重下
async function verifyFile(filePath, expectedSha256) {
const hash = crypto.createHash('sha256');
const stream = fs.createReadStream(filePath); // 流式计算,不一次读入内存
stream.on('data', (d) => hash.update(d));
await once(stream, 'end');
return hash.digest('hex') === expectedSha256;
}

4. 异常处理清单​

  • 网络断开:分片级重试(指数退避),单个分片失败不拖垮整个任务
  • 磁盘不足:下载前 statfs 检查剩余空间
  • 应用退出:downloading → 持久化为 paused,启动后恢复
  • 系统休眠:配合 powerMonitor,休眠暂停/唤醒续传
  • 临时文件:下载中文件名加 .download 后缀,完成后 rename(避免用户打开半成品)

5. 现成方案评估​

  • electron-dl:简单场景够用,但断点续传/分片能力弱
  • 商业 CDN SDK(又拍/阿里 OSS 分片下载):大文件场景直接用服务端的断点能力
  • 自研:agent 场景(后台下载升级包、资源包)基本都要自研任务队列

面试话术​

设计要点五个:HEAD 探测 Range 支持;分片 + 进度元数据落盘实现跨重启续传;并发分片 + 任务队列 + 限速;ETag + sha256 双重校验防脏数据;配合电源事件和网络重试的完整异常路径。临时文件加后缀、完成 rename 是细节但体现工程素养。

常见追问​

  • 上传断点续传呢? 更复杂,需要服务端支持(分片上传协议如 OSS multipart / tus 协议),客户端记录 uploadId 和已传分片。
  • 进度怎么推给 UI? 主进程聚合进度(按分片完成数/字节数),节流后 broadcast,不要每个分片都推。